#include <string.h>
#if defined(USE_GNUTLS)
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#elif defined(USE_WOLFSSL)
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/hmac.h>
#elif defined(USE_MBEDTLS)
#include <mbedtls/md.h>
#elif defined(__APPLE__) && !defined(USE_OPENSSL)
#define USE_CC_CRYPT 1
#include <CommonCrypto/CommonDigest.h>
#elif defined(_WIN32) && !defined(USE_OPENSSL)
#define USE_WIN_CRYPT 1
#include <windows.h>
#include <wincrypt.h>
#else
#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#define OPENSSL_3_AND_LATER 1
#include <openssl/evp.h>
#else
#include <openssl/sha.h>
#endif
#endif
#include "hmac.h"

#ifdef USE_CC_CRYPT
#define SHA256_CTX CC_SHA256_CTX
#define SHA256_Init CC_SHA256_Init
#define SHA256_Update CC_SHA256_Update
#define SHA256_Final CC_SHA256_Final
#endif

typedef struct
{
#ifdef USE_WIN_CRYPT
	HCRYPTPROV prov;
	HCRYPTHASH sha;
#elif defined(USE_GNUTLS)
	gnutls_hmac_hd_t hmac;
#elif defined(OPENSSL_3_AND_LATER)
	EVP_MD_CTX *basectx;
	EVP_MD_CTX *mdctx;
#elif defined(USE_WOLFSSL)
	Hmac hmac;
#elif defined(USE_MBEDTLS)
	mbedtls_md_context_t hmac;
#else
	SHA256_CTX sha;
#endif
	unsigned char keybuf[64];
} hmac_sha256_t;

static void HMAC_SHA256_Init(hmac_sha256_t *hmac, const void *key, size_t length)
{
#ifdef USE_WIN_CRYPT
	int i;
	CryptAcquireContext(&hmac->prov,NULL,NULL,PROV_RSA_AES,CRYPT_VERIFYCONTEXT);
	if(length > 64) {
		unsigned char digest[32];
		HCRYPTHASH sha;
		DWORD bufLength = 32;
		CryptCreateHash(hmac->prov,CALG_SHA_256,0,0,&sha);
		CryptHashData(sha,(PBYTE)key,(DWORD)length,0);
		CryptGetHashParam(sha,HP_HASHVAL,hmac->keybuf,&bufLength,0);
		CryptDestroyHash(sha);
	}
	else memcpy(hmac->keybuf,key,length);
	for(i=length;i<64;i++) hmac->keybuf[i] = 0;
	for(i=0;i<64;i++) hmac->keybuf[i] ^= 0x36;
	CryptCreateHash(hmac->prov,CALG_SHA_256,0,0,&hmac->sha);
	CryptHashData(hmac->sha,hmac->keybuf,64,0);
#elif defined(USE_GNUTLS)
	gnutls_hmac_init(&hmac->hmac, GNUTLS_MAC_SHA256, key, length);
#elif defined(OPENSSL_3_AND_LATER)
	size_t i;
	hmac->basectx = EVP_MD_CTX_new();
	hmac->mdctx = EVP_MD_CTX_new();
	EVP_DigestInit_ex(hmac->basectx, EVP_sha256(), NULL);
	if (length > 64) {
		EVP_MD_CTX_copy_ex(hmac->mdctx, hmac->basectx);
		EVP_DigestUpdate(hmac->mdctx, key, length);
		EVP_DigestFinal_ex(hmac->mdctx, hmac->keybuf, NULL);
		length = 32;
	}
	else memcpy(hmac->keybuf, key, length);
	for (i=length; i<64; i++) hmac->keybuf[i] = 0;
	for (i=0; i<64; i++) hmac->keybuf[i] ^= 0x36;
	EVP_MD_CTX_copy_ex(hmac->mdctx, hmac->basectx);
	EVP_DigestUpdate(hmac->mdctx, hmac->keybuf, 64);
#elif defined(USE_WOLFSSL)
	wc_HmacSetKey(&hmac->hmac, WC_SHA256, key, length);
#elif defined(USE_MBEDTLS)
	mbedtls_md_init(&hmac->hmac);
	mbedtls_md_setup(&hmac->hmac, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
	mbedtls_md_hmac_starts(&hmac->hmac, key, length);
#else
	size_t i;
	if(length > 64) {
		unsigned char digest[32];
		SHA256_CTX sha;
		SHA256_Init(&sha);
		SHA256_Update(&sha,key,length);
		SHA256_Final(digest,&sha);
		memcpy(hmac->keybuf,digest,32);
		length = 32;
	}
	else memcpy(hmac->keybuf,key,length);
	for(i=length;i<64;i++) hmac->keybuf[i] = 0;
	for(i=0;i<64;i++) hmac->keybuf[i] ^= 0x36;
	SHA256_Init(&hmac->sha);
	SHA256_Update(&hmac->sha,hmac->keybuf,64);
#endif
}

static void HMAC_SHA256_Update(hmac_sha256_t *hmac, const void *data, size_t length)
{
#ifdef USE_WIN_CRYPT
	CryptHashData(hmac->sha,(PBYTE)data,(DWORD)length,0);
#elif defined(USE_GNUTLS)
	gnutls_hmac(hmac->hmac, data, length);
#elif defined(OPENSSL_3_AND_LATER)
	EVP_DigestUpdate(hmac->mdctx, data, length);
#elif defined(USE_WOLFSSL)
	wc_HmacUpdate(&hmac->hmac, data, length);
#elif defined(USE_MBEDTLS)
	mbedtls_md_hmac_update(&hmac->hmac, data, length);
#else
	SHA256_Update(&hmac->sha,data,length);
#endif
}

static void HMAC_SHA256_Final(hmac_sha256_t *hmac, unsigned char *md)
{
#ifdef USE_WIN_CRYPT
	DWORD i, bufLength = 32;
	CryptGetHashParam(hmac->sha,HP_HASHVAL,md,&bufLength,0);
	CryptDestroyHash(hmac->sha);
	HCRYPTHASH sha;
	CryptCreateHash(hmac->prov,CALG_SHA_256,0,0,&sha);
	for(i=0;i<64;i++) hmac->keybuf[i] ^= 0x36 ^ 0x5c;
	CryptHashData(sha,hmac->keybuf,64,0);
	CryptHashData(sha,md,32,0);
	CryptGetHashParam(sha,HP_HASHVAL,md,&bufLength,0);
	CryptDestroyHash(sha);
	CryptReleaseContext(hmac->prov, 0);
#elif defined(USE_GNUTLS)
	gnutls_hmac_deinit(hmac->hmac, md);
#elif defined(OPENSSL_3_AND_LATER)
	int i;
	EVP_DigestFinal_ex(hmac->mdctx, md, NULL);
	EVP_MD_CTX_copy_ex(hmac->mdctx, hmac->basectx);
	for (i=0; i<64; i++) hmac->keybuf[i] ^= 0x36 ^ 0x5c;
	EVP_DigestUpdate(hmac->mdctx, hmac->keybuf, 64);
	EVP_DigestUpdate(hmac->mdctx, md, 32);
	EVP_DigestFinal_ex(hmac->mdctx, md, NULL);
	EVP_MD_CTX_free(hmac->mdctx);
	EVP_MD_CTX_free(hmac->basectx);
#elif defined(USE_WOLFSSL)
	wc_HmacFinal(&hmac->hmac, md);
#elif defined(USE_MBEDTLS)
	mbedtls_md_hmac_finish(&hmac->hmac, md);
	mbedtls_md_free(&hmac->hmac);
#else
	int i;
	SHA256_Final(md,&hmac->sha);
	SHA256_CTX sha;
	SHA256_Init(&sha);
	for(i=0;i<64;i++) hmac->keybuf[i] ^= 0x36 ^ 0x5c;
	SHA256_Update(&sha,hmac->keybuf,64);
	SHA256_Update(&sha,md,32);
	SHA256_Final(md,&sha);
#endif
}

void proxy2ch_HMAC_SHA256(const void *key, size_t keyLength, const void *data, size_t dataLength, void *macOut)
{
	hmac_sha256_t hmac;
	HMAC_SHA256_Init(&hmac, key, keyLength);
	HMAC_SHA256_Update(&hmac, data, dataLength);
	HMAC_SHA256_Final(&hmac, macOut);
}
